package com.apes.framework.util;

import com.apes.framework.jpa.ApesBean;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.springframework.lang.Nullable;
import org.springframework.util.LinkedCaseInsensitiveMap;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 功能：reflect 工具
 *
 * @author xul
 * @create 2018-02-01 19:26
 */
public class ReflectUtil {

    public static Object getPrivateField(Object instance, String filedName) {
        try {
            Field field = instance.getClass().getDeclaredField(filedName);
            field.setAccessible(true);
            return field.get(instance);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 功能：根据字段名获取实体Field
     */
    public static Field getField(Object entity, String fieldName) {
        Class clazz = entity.getClass();
        return getField(clazz, fieldName);
    }

    /**
     * 功能：根据字段名获取实体Field
     */
    public static Field getField(Class<?> clazz, String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
        } catch (SecurityException e) {
            throw new RuntimeException("not allowed to access field " + field + " on class " + clazz.getCanonicalName());
        } catch (NoSuchFieldException e) {
            Class<?> superClass = clazz.getSuperclass();
            if (superClass != null) {
                return getField(superClass, fieldName);
            }
        }
        return field;
    }

    /**
     * 功能：根据字段名获取实体字段描述
     */
    public static PropertyDescriptor getFieldPropertyDescriptor(Object target, String fieldName) {
        return getFieldPropertyDescriptor(target.getClass(), fieldName);
    }

    /**
     * 功能：根据字段名获取类的字段描述
     */
    public static PropertyDescriptor getFieldPropertyDescriptor(Class clazz, String fieldName) {
        PropertyDescriptor[] propertyDescriptors = ReflectUtil.getPropertyDescriptors(clazz);
        if (propertyDescriptors == null) return null;
        try {
            return Arrays.stream(propertyDescriptors).filter(descriptor -> descriptor.getName().equals(fieldName)).findFirst().get();
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 功能：根据字段名获取实体字段值
     */
    public static <T> T getFieldValue(Object entity, String fieldName) {
        PropertyDescriptor descriptor = getFieldPropertyDescriptor(entity.getClass(), fieldName);
        if (descriptor == null) return null;
        return getFieldValue(entity, descriptor);
    }

    /**
     * 功能：根据字段描述获取实体字段值
     */
    public static <T> T getFieldValue(Object entity, PropertyDescriptor descriptor) {
        Method getter = descriptor.getReadMethod();
        try {
            return getter != null ? (T) getter.invoke(entity) : null;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(Tools.getThrowable(e).getMessage());
        }
    }

    /**
     * 功能：获取实体类指定注解
     */
    public static <T> T getAnnotation(Class c, Class<T> annotationClass) {
        return (T) c.getAnnotation(annotationClass);
    }

    /**
     * 功能：根据字段名获取实体字段上指定的注解
     */
    public static <T> T getAnnotation(Object target, String fieldName, Class<T> annotationClass) {
        return getAnnotation(getClass(target), fieldName, annotationClass);
    }

    /**
     * 功能：根据字段名获取实体字段上指定的注解
     */
    public static <T> T getAnnotation(Class clazz, String fieldName, Class<T> annotationClass) {
        if (!annotationClass.isAnnotation()) return null;
        ArrayList<Annotation> annotations = getAnnotations(clazz, fieldName);
        if (annotations.isEmpty()) return null;
        return (T) annotations.stream().filter(a -> annotationClass.isInstance(a)).findAny().orElse(null);
    }

    /**
     * 功能：获取字段上指定的注解
     */
    public static <T> T getAnnotation(Field f, Class annotationClass) {
        return (T) f.getAnnotation(annotationClass);
    }

    private static Cache<String, ArrayList<Annotation>> annotationsCache = CacheBuilder.newBuilder()
            .maximumSize(50000)  /**设置缓存的最大容量**/
            .expireAfterAccess(60, TimeUnit.MINUTES)  /**设置缓存项在60分钟内没有被访问，则该缓存项会失效**/
            .build();

    public static ArrayList<Annotation> getAnnotations(Object target, String fieldName) {
        Class clazz = getClass(target);
        return getAnnotations(clazz, fieldName);
    }

    public static ArrayList<Annotation> getAnnotations(Class clazz, String fieldName) {
        String key = concat(clazz.getName(), fieldName);
        ArrayList<Annotation> result = annotationsCache.getIfPresent(key);
        if (result == null) {
            result = new ArrayList<>();
            Annotation[] annotations = null;
            Field f = getField(clazz, fieldName);
            if (f != null) {
                annotations = getAnnotations(f);
            }
            if (annotations != null && annotations.length > 0) {
                Collections.addAll(result, annotations);
            }
            annotationsCache.put(key, result);
        }
        return result;
    }

    /**字符拼接**/
    public static String concat(String... fields) {
        if (fields == null || fields.length == 0) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        for (String field : fields) {
            if (sb.length() > 0) {
                sb.append('|');
            }
            sb.append(field);
        }
        return sb.toString();
    }

    public static Class getClass(Object obj) {
        Class clazz;
        if (obj instanceof ApesBean) {
            clazz = JpaUtil.getDomainClass((ApesBean) obj);
        } else {
            clazz = obj.getClass();
        }
        return clazz;
    }

    /**
     * 功能：获取字段上所有的注解
     */
    public static Annotation[] getAnnotations(Field f) {
        return f.getAnnotations();
    }

    public static boolean existAnnotation(Object target, String fieldName, Class annotationClass) {
        return existAnnotation(target.getClass(), fieldName, annotationClass);
    }

    public static boolean existAnnotation(Class clazz, String fieldName, Class annotationClass) {
        Field f = getField(clazz, fieldName);
        if (f == null) return false;
        return existAnnotation(f, annotationClass);
    }

    public static <T extends Annotation> T[] getAnnotationsByType(Class clazz, String fieldName, Class<T> annotationClass) {
        Field f = getField(clazz, fieldName);
        if (f == null) return null;
        return f.getAnnotationsByType(annotationClass);
    }

    /**
     * 功能：判断指定注解在字段上是否存在
     */
    public static boolean existAnnotation(Field f, Class annotationClass) {
        Annotation annotation = getAnnotation(f, annotationClass);
        return annotation != null;
    }

    /**
     * 功能：获取实体的所有字段描述
     */
    public static PropertyDescriptor[] getPropertyDescriptors(Object entity) {
        Class clazz = entity.getClass();
        return getPropertyDescriptors(clazz);
    }

    /**
     * 功能：获取实体的所有字段名
     */
    public static List<String> getPropertyName(Object entity) {
        PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(entity);
        return Arrays.stream(propertyDescriptors).map(descriptor -> descriptor.getName()).collect(Collectors.toList());
    }

    /**
     * 功能：获取实体的所有字段描述
     */
    public static PropertyDescriptor[] getPropertyDescriptors(Class clazz) {
        BeanInfo beanInfo;
        try {
            beanInfo = Introspector.getBeanInfo(clazz);
        } catch (IntrospectionException e) {
            return null;
        }
        return beanInfo.getPropertyDescriptors();
    }

    /**
     * 功能：获取field的类型，如果是复合对象，获取的是泛型的类型
     */
    private static Class getFieldClass(Field field) {
        Class fieldClazz = field.getType();
        if (fieldClazz.isAssignableFrom(List.class)) {
            Type fc = field.getGenericType(); // 关键的地方，如果是List类型，得到其Generic的类型
            if (fc instanceof ParameterizedType) { // 如果是泛型参数的类型
                ParameterizedType pt = (ParameterizedType) fc;
                fieldClazz = (Class) pt.getActualTypeArguments()[0]; //得到泛型里的class类型对象。
            }
        }
        return fieldClazz;
    }

    private static Class<?>[] clazzz = {
            String.class, Float.class, Double.class,
            Integer.class, Long.class, Boolean.class,
            Byte.class, Short.class, Character.class};

    /**
     * 功能：判断Class是否基本类型以及与其对应的包装类
     */
    public static boolean isPrimitive(Class<?> clazz) {
        if (clazz.isPrimitive()) return true;
        for (Class<?> c : clazzz) {
            if (c.isAssignableFrom(clazz)) return true;
        }
        return false;
    }

    /**
     * 功能：给字段赋值
     */
    public static void setField(Field field, Object entity, Object value) {
        try {
            field.setAccessible(true);
            field.set(entity, value);
        } catch (IllegalArgumentException e) {
            throw new RuntimeException("Could not set field " + field.toString(), e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Could not set field " + field.toString(), e);
        }
    }

    /**
     * 功能：根据字段描述获取实体字段值
     */
    public static void setField(Object entity, PropertyDescriptor descriptor, Object value) {
        Method setter = descriptor.getWriteMethod();
        try {
            if (setter != null) setter.invoke(entity, value);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void setField(Object entity, String fieldName, Object value) {
        PropertyDescriptor descriptor = getFieldPropertyDescriptor(entity.getClass(), fieldName);
        if (descriptor == null) return;
        setField(entity, descriptor, value);
    }

    /**
     * Returns the setter-method for the given field name or null if no setter exists.
     */
    public static Method getSetter(String fieldName, Class<?> clazz, Class<?> fieldType) {
        String setterName = "set" + Character.toTitleCase(fieldName.charAt(0)) + fieldName.substring(1, fieldName.length());
        try {
            // Using getMethods(), getMethod(...) expects exact parameter type
            // matching and ignores inheritance-tree.
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if (method.getName().equals(setterName)) {
                    Class<?>[] paramTypes = method.getParameterTypes();
                    if (paramTypes != null && paramTypes.length == 1 && paramTypes[0].isAssignableFrom(fieldType)) {
                        return method;
                    }
                }
            }
            return null;
        } catch (SecurityException e) {
            throw new RuntimeException("Not allowed to access method " + setterName + " on class " + clazz.getCanonicalName());
        }
    }

    public static void invokeSetter(Method setterMethod, Object target, String name, Object value) {
        try {
            setterMethod.invoke(target, value);
        } catch (IllegalArgumentException e) {
            throw new RuntimeException("Error while invoking '" + name + "' on class " + target.getClass().getName(), e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Illegal access when calling '" + name + "' on class " + target.getClass().getName(), e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException("Exception while invoking '" + name + "' on class " + target.getClass().getName(), e);
        }
    }


    public static Object invoke(Object target, String methodName, Object[] args) {
        try {
            Class<? extends Object> clazz = target.getClass();
            Method method = findMethod(clazz, methodName, args);
            method.setAccessible(true);
            return method.invoke(target, args);
        } catch (Exception e) {
            throw new RuntimeException("couldn't invoke " + methodName + " on " + target, e);
        }
    }

    public static void invokeSetterOrField(Object target, String name, Object value, boolean throwExceptionOnMissingField) {
        Method setterMethod = getSetter(name, target.getClass(), value.getClass());

        if (setterMethod != null) {
            invokeSetter(setterMethod, target, name, value);
        } else {
            Field field = ReflectUtil.getField(target, name);
            if (field == null) {
                if (throwExceptionOnMissingField) {
                    throw new RuntimeException("Field definition uses unexisting field '" + name + "' on class " + target.getClass().getName());
                } else {
                    return;
                }
            }

            // Check if the delegate field's type is correct
            if (!fieldTypeCompatible(value, field)) {
                throw new RuntimeException("Incompatible type set on field declaration '" + name
                        + "' for class " + target.getClass().getName()
                        + ". Declared value has type " + value.getClass().getName()
                        + ", while expecting " + field.getType().getName());
            }

            setField(field, target, value);
        }
    }

    private static boolean fieldTypeCompatible(Object value, Field field) {
        if (value != null) {
            return field.getType().isAssignableFrom(value.getClass());
        } else {
            return true;
        }
    }

    private static Method findMethod(Class<? extends Object> clazz, String methodName, Object[] args) {
        for (Method method : clazz.getDeclaredMethods()) {
            // TODO add parameter matching
            if (method.getName().equals(methodName) && matches(method.getParameterTypes(), args)) {
                return method;
            }
        }
        Class<?> superClass = clazz.getSuperclass();
        if (superClass != null) {
            return findMethod(superClass, methodName, args);
        }
        return null;
    }

    private static boolean matches(Class<?>[] parameterTypes, Object[] args) {
        if ((parameterTypes == null) || (parameterTypes.length == 0)) {
            return ((args == null) || (args.length == 0));
        }
        if ((args == null) || (parameterTypes.length != args.length)) {
            return false;
        }
        for (int i = 0; i < parameterTypes.length; i++) {
            if ((args[i] != null) && (!parameterTypes[i].isAssignableFrom(args[i].getClass()))) {
                return false;
            }
        }
        return true;
    }

    public static void copy(Object source, Object target, String... fields) {
        Arrays.stream(fields).forEach(field -> {
            Object value = getFieldValue(source, field);
            setField(target, field, value);
        });
    }

    public static void copyAll(Object source, Object target, String... excludes) {
        getPropertyName(target).stream().filter(fieldName -> LogicalUtil.notIn(fieldName, excludes)).forEach(fieldName -> {
            Object value = getFieldValue(source, fieldName);
            if (value != null) setField(target, fieldName, value);
        });
    }

    public static <T> T clone(T source, String... excludes) {
        try {
            T target = (T) source.getClass().newInstance();
            getPropertyName(target).stream().filter(fieldName -> LogicalUtil.notIn(fieldName, excludes))
                    .forEach(fieldName -> {
                        Object value = getFieldValue(source, fieldName);
                        if (value != null) setField(target, fieldName, value);
                    });
            return target;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static Class getClass(String className) {
        Class clazz;
        try {
            clazz = Thread.currentThread().getContextClassLoader().loadClass(className);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        return clazz;
    }


    /**
     * 使用对象序列化和反序列化实现深度克隆
     *
     * @param source
     * @return
     */
    public static <T> T depthClone(T source) {
        T cloneObj = null;
        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ObjectOutputStream oo = new ObjectOutputStream(out);
            oo.writeObject(source);
            ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
            ObjectInputStream oi = new ObjectInputStream(in);
            cloneObj = (T) oi.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return cloneObj;
    }

    /**
     * 对象转Map
     **/
    public static Map<String, Object> getMerge(Object source) {
        Map<String, Object> toMerge = new LinkedCaseInsensitiveMap<>();
        Arrays.stream(getPropertyDescriptors(source)).forEach(descriptor -> {
            Object value = getFieldValue(source, descriptor);
            toMerge.put(descriptor.getName(), value);
        });
        return toMerge;
    }

    public static void doWithFields(Class<?> clazz, FieldCallback<PropertyDescriptor> fc) {
        doWithFields(clazz, fc, null);
    }

    public static void doWithFields(Class<?> clazz, FieldCallback<PropertyDescriptor> fc, @Nullable FieldFilter<PropertyDescriptor> ff) {
        // Keep backing up the inheritance hierarchy.
        Class<?> targetClass = clazz;
        do {
            PropertyDescriptor[] descriptors = getPropertyDescriptors(targetClass);
            if (descriptors == null) return;
            for (PropertyDescriptor descriptor : descriptors) {
                if (ff != null && !ff.matches(descriptor)) {
                    continue;
                }
                try {
                    fc.doWith(descriptor);
                } catch (IllegalAccessException ex) {
                    throw new IllegalStateException("Not allowed to access field '" + descriptor.getName() + "': " + ex);
                }
            }
            targetClass = targetClass.getSuperclass();
        }
        while (targetClass != null && targetClass != Object.class);
    }

    /**
     * Callback interface invoked on each field in the hierarchy.
     */
    @FunctionalInterface
    public interface FieldCallback<T> {

        /**
         * Perform an operation using the given field.
         *
         * @param f the field to operate on
         */
        void doWith(T f) throws IllegalArgumentException, IllegalAccessException;
    }


    /**
     * Callback optionally used to filter fields to be operated on by a field callback.
     */
    @FunctionalInterface
    public interface FieldFilter<T> {

        /**
         * Determine whether the given field matches.
         *
         * @param f the field to check
         */
        boolean matches(T f);
    }
}
